
#ifndef _SHAPE3D_LOADER_H__
#define _SHAPE3D_LOADER_H__

#include <CGAL/Polyhedron_incremental_builder_3.h>

#include "ply.h"

using CGAL::Modifier_base; 

using namespace std;

template <class HDS>
class LoaderPLY : public Modifier_base<HDS> {
   
private:
   /// A local structure used to store aditional information that may 
   /// be associated with each vertex in the PLY file format
   typedef struct Vertex {
      float x, y, z;          // vertex coordinates
      float red, green, blue; // color components
      float nx, ny, nz;       // normal vector
      void *other_props;      // pointer to other properties
   } Vertex;
   
   /// A local structure used to store aditional information that may be 
   /// associated with each facet in the PLY file format
   typedef struct Face {
      unsigned char nverts;    // number of vertex indices in list
      int *verts;              // vertex index list
      void *other_props;       // other properties
   } Face;
   
   char *elem_names[2];       // vector that stores two element names 
   
public:
   int nverts,                // number of vertices 
   nfaces;                    // number of facets 
   Vertex **vlist;            // list of vertices 
   Face **flist;              // list of facets 
   
   PlyOtherProp   *vert_other, // pointer to other vertex properties 
                  *face_other; // pointer to other facet properties 
   
   int per_vertex_color;      // color per vertex
   int has_normals;           // indicates if the vertex has normals
   
public:
   const char* filename;       // name of the .ply file
   
   /// Constructor 
   /// @param f name of a .ply file that stores the model
   LoaderPLY (const char* f) : filename(f) {  
      vlist = 0;
      flist = 0;
      vert_other = 0;
      face_other = 0;
   }
   
   /// Loads the PLY model and build the CGAL Halfedge Data Structure
   /// @param hds - halfedge data structure.
   void operator () (HDS& hds) {
      
      cout <<"Loading PLY model ..." << endl;
      per_vertex_color = 0;
      has_normals      = 0;
      
      // list of property information for a vertex
      PlyProperty vert_props[] = { 
         {"x", Float32, Float32, offsetof(Vertex,x), 0, 0, 0, 0},
         {"y", Float32, Float32, offsetof(Vertex,y), 0, 0, 0, 0},
         {"z", Float32, Float32, offsetof(Vertex,z), 0, 0, 0, 0},
         {"red", Float32, Float32, offsetof(Vertex,red), 0, 0, 0, 0},
         {"green", Float32, Float32, offsetof(Vertex,green), 0, 0, 0, 0},
         {"blue", Float32, Float32, offsetof(Vertex,blue), 0, 0, 0, 0},
         {"nx", Float32, Float32, offsetof(Vertex,nx), 0, 0, 0, 0},
         {"ny", Float32, Float32, offsetof(Vertex,ny), 0, 0, 0, 0},
         {"nz", Float32, Float32, offsetof(Vertex,nz), 0, 0, 0, 0},
      };
   
      // list of property information for a face
      PlyProperty face_props[] = {
         {"vertex_indices", 
            Int32, 
            Int32, 
            offsetof(Face,verts),
            1, 
            Uint8, 
            Uint8, 
            offsetof(Face,nverts)
            },
      };
   
      elem_names[0] = "vertex"; 
      elem_names[1] = "face";
      
      int i,j;
      int elem_count;
      char *elem_name=0;
      PlyFile *in_ply=0;
      FILE* arq=0;
      
      arq = fopen(filename, "r");
      in_ply  = read_ply (arq);
      for (i = 0; i < in_ply->num_elem_types; i++) {
         elem_name = setup_element_read_ply (in_ply, i, &elem_count);
         if (equal_strings ("vertex", elem_name)) {
            vlist = (Vertex **) malloc (sizeof (Vertex *) * elem_count);
            nverts = elem_count;
            setup_property_ply (in_ply, &vert_props[0]);
            setup_property_ply (in_ply, &vert_props[1]);
            setup_property_ply (in_ply, &vert_props[2]);
            
            for (j = 0; j < in_ply->elems[i]->nprops; j++) {
               PlyProperty *prop;
               prop = in_ply->elems[i]->props[j];
               if (equal_strings ("red", prop->name)) {
                  setup_property_ply (in_ply, &vert_props[3]);
                  per_vertex_color = 1;
               }
               if (equal_strings ("green", prop->name)) {
                  setup_property_ply (in_ply, &vert_props[4]);
                  per_vertex_color = 1;
               }
               if (equal_strings ("blue", prop->name)) {
                  setup_property_ply (in_ply, &vert_props[5]);
                  per_vertex_color = 1;
               }
               if (equal_strings ("nx", prop->name)) {
                  setup_property_ply (in_ply, &vert_props[6]);
                  has_normals = 1;
               }
               if (equal_strings ("ny", prop->name)) {
                  setup_property_ply (in_ply, &vert_props[7]);
                  has_normals = 1;
               }
               if (equal_strings ("nz", prop->name)) {
                  setup_property_ply (in_ply, &vert_props[8]);
                  has_normals = 1;
               }
            }
            vert_other = get_other_properties_ply (in_ply, 
            offsetof(Vertex,other_props));
            for (j = 0; j < elem_count; j++) {
               vlist[j] = (Vertex *) malloc (sizeof (Vertex));
               vlist[j]->red = 255;
               vlist[j]->green = 0;
               vlist[j]->blue = 0;
               get_element_ply (in_ply, (void *) vlist[j]);
            }
         }
         else 
            if (equal_strings ("face", elem_name)) {
               flist = (Face **) malloc (sizeof (Face *) * elem_count);
               nfaces = elem_count;
               setup_property_ply (in_ply, &face_props[0]);
               face_other = get_other_properties_ply (in_ply, offsetof(Face,other_props));
               for (int j = 0; j < elem_count; j++) {
                  flist[j] = (Face *) malloc (sizeof (Face));
                  get_element_ply (in_ply, (void *) flist[j]);
               }
            }
            else
               get_other_element_ply (in_ply);
      }
      close_ply (in_ply);
      free_ply (in_ply);
      
      typedef typename HDS::Vertex   Vertex;
      typedef typename Vertex::Point Point;
      CGAL::Polyhedron_incremental_builder_3<HDS> B(hds, true);
      B.begin_surface(nverts, nfaces);
      for (int i = 0 ; i < nverts ; i++) {
         B.add_vertex (Point (vlist[i]->x, vlist[i]->y, vlist[i]->z) );
      }
      
      for (int i = 0 ; i < nfaces ; i++) {
         B.begin_facet();
         //~ for (int j = flist[i]->nverts - 1; j >= 0; j--) {
         for (int j = 0; j < flist[i]->nverts; j++) {
            B.add_vertex_to_facet (flist[i]->verts[j]);
         }
         B.end_facet();
      }
      B.end_surface ();
      cout <<"Loading PLY model ... done " << endl;
   }
};

#endif
